package com.example.demokotlin

import org.junit.Test


/**
 * 函数式编程测试
 * 一个函数式应用包括三部分：
 * 变换transform
 * 过滤filter
 * 组合combine
 *
 * 每个函数都是针对集合而设计，目标是产生一个最终结果。
 * 函数式编程用到的函数生来就是可组合的。
 *
 * @author zhouronghua
 * @time 2021/9/18 8:39 上午
 */
class TransformTest {

    /**
     * 方式一：映射(map/mapIndexed)
     * 变换是函数式编程的第一大类函数集合，
     * 1）map变换函数会遍历接收者，让变换器函数作用域集合内的每一个元素，
     * 返回变换后的集合
     * 2）返回的新集合可以和原集合是不同类型的
     */
    @Test
    fun testMap() {
        val animals = listOf("zebra", "giraffe", "elephant", "rat")
        val babies = animals
            .map { it.capitalize() }
            .map {
                "A baby $it"
            }
        // 变换后的数据通过返回值接收变换后的结果
        println(babies)
        // 经过变换后原集合不会改变
        println(animals)
        // 返回集合类型变换
        println("=== Map返回新的集合类型 ===")
        // 返回键值对列表
        val mm = animals.map {
            it to it.length
        }
        println(mm)
        // 和索引一起进行变化
        val babiesNo = animals.mapIndexed { index, value -> "$value $index" }
        println(babiesNo)
    }

    /**
     * 字符串转换
     * joinToString根据提供的参数从集合元素构建单个 String
     * joinTo() 执行相同的操作，但将结果附加到给定的 Appendable 对象。
     *
     * @author zhouronghua
     * @time 2021/9/18 11:34 上午
     */
    @Test
    fun testStringJoin() {
        println("=== joinToString将集合构建字符串 ===")
        val numbers = listOf("one", "two", "three", "four")
        println(numbers)
        // 字符串拼接
        println(numbers.joinToString())
        println("=== joinTo将结果附加到给定的Appendable 对象 ===")
        // 测试joinTo
        val sb = StringBuffer("Show list: ")
        println(numbers.joinTo(sb))
        // 格式化字符串拼接
        println(
            numbers.joinToString(
                separator = " | ",
                prefix = "start format: ",
                postfix = ": is end"
            )
        )
        // 格式化字符串输出：截取列表的一部分值进行字符串拼接
        val numList = (1 .. 100).toList();
        // 获取前十个元素，后面的元素采用...替代
        println(numList.joinToString(limit = 10, truncated = "..."))
        // 字符串转换(转换为大写输出)
        println(numbers.joinToString { it.toUpperCase() })
    }

    /**
     * 方式二：压缩(zip/unzip)
     * 压缩zip是根据两个集合中具有相同位置的元素构建配对。
     * 在一个集合（或数组）上以另一个集合（或数组）作为参数调用时，zip() 返回 Pair 对象的列表（List）。
     * 接收者集合的元素是这些配对中的第一个元素。 如果集合的大小不同，则 zip() 的结果为较小集合的大小；
     * 结果中不包含较大集合的后续元素。 zip() 也可以中缀形式调用 a zip b 。
     *
     * @author zhouronghua
     * @time 2021/9/18 1:56 下午
     */
    @Test
    fun testZip() {
        println("=== zip将两个集合相同位置元素配对 ===")
        val colors = listOf("red", "yellow", "blue", "white")
        val animals = listOf("fox", "dog", "bear", "wolf", "cat")
        // 返回List<Pair<String, String>>
        val pairs = colors zip animals
        println("pars length: ${pairs.size}, $pairs")
        // 定义压缩变换函数（zip变换后生成一个新的列表）
        println(colors.zip(animals) { color, animal ->
            "The $animal is $color"
        })
        // unzip从键值对列表反向解构出两个列表
        val personPairs =
            listOf("Chinese" to "中国人", "English" to "英国人", "American" to "美国人", "Japanese" to "日本人")
        val pair = personPairs.unzip()
        // 第一个列表是Pair.Key列表
        println(pair.first)
        // 第一个列表是Pair.Value列表
        println(pair.second)
    }

    /**
     * 方式三：关联(associateWith/associateBy/associate)
     * 关联 转换允许从集合元素和与其关联的某些值构建 Map。 在不同的关联类型中，元素可以是关联 Map 中的键或值。
     * 基本的关联函数 associateWith() 创建一个 Map，其中原始集合的元素是键，并通过给定的转换函数从中产生值。
     *  associateBy()。 它需要一个函数，该函数根据元素的值返回键。如果两个元素相等，则仅最后一个保留在 Map 中。 还可以使用值转换函数来调用 associateBy()。
     * 如果两个元素相等，则仅最后一个保留在 Map 中。
     * associate()另一种构建 Map 的方法是使用函数，其中 Map 键和值都是通过集合元素生成的。
     * 它需要一个 lambda 函数，该函数返回 Pair：键和相应 Map 条目的值。
     *
     * @author zhouronghua
     * @time 2021/9/18 2:16 下午
     */
    @Test
    fun testAssociate() {
        println("=== associateWith将元素作为Key生成对应的值 ===")
        val colors = listOf("red", "yellow", "yellowy", "blue", "white")
        val colorMap = colors.associateWith { it -> it.length }
        println(colorMap)
        // 根据列表元素获取Map的键，相同键采取后一个元素的值
        // ("yellow", "yellowy")生成的键相同，采用后一个yellowy作为值
        val keyMap = colors.associateBy { it -> it.first().toInt() }
        println(keyMap)
        // 名字分割(姓为键，名为值)
        val names = listOf("李世民", "王冕", "郑文公", "宋香儿")
        val nameMap = names.associate { "${it.first()}姓" to it.substring(1) }
        println(nameMap)
    }

    /**
     * 方法四: 打平(flatten/flatMap)
     *  flatten()。可以在一个集合的集合（例如，一个 Set 组成的 List）上调用它。 该函数返回嵌套集合中的所有元素的一个 List。
     *  flatMap() 提供了一种灵活的方式来处理嵌套的集合。 它需要一个函数将一个集合元素映射到另一个集合。
     *  flatMap() 返回单个列表其中包含所有元素的值。 所以，flatMap() 表现为 map()（以集合作为映射结果）与 flatten() 的连续调用。
     *
     * @author zhouronghua
     * @time 2021/9/18 4:16 下午
     */
    @Test
    fun testFlatten() {
        println("=== flatten将将集合元素打平到一个List ===")
        val numSets = listOf(setOf(1, 2, 3), setOf(4, 5, 6), setOf(7, 8, 9), listOf(10, 11, 12))
        println(numSets.flatten())
        // 测试将嵌套集合的元素打平到一个List
        val containers = listOf(
            "words" to listOf("one", "two", "three"),
            "colors" to listOf("red", "yellow", "blue"),
            "numbers" to setOf(1, 2, 3)
        )
        // Pair对的值进行打平
        println(containers.flatMap { it -> it.second })
    }
}
